/*
 * Copyright (c) 2010-2011 NOO. All Rights Reserved.
 * [Id:YamDataModalProvder.java  11-10-17 下午10:54 poplar.yfyang ]
 */
package org.mumu.pawn.fm.dm;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.mumu.pawn.fm.util.QueryField;
import org.richfaces.component.SortOrder;
import org.richfaces.model.Arrangeable;
import org.richfaces.model.ArrangeableState;
import org.richfaces.model.FilterField;
import org.richfaces.model.SortField;
import org.richfaces.renderkit.DataTableRenderer;

import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 数据列表的查询分页等信息的处理基础类.包括处理排序问题。
 * {@link Arrangeable}说明：ExtendedDataModel基础扩展的数据模型，增加了数据的过滤和排序机制。
 * </p>
 * <p/>
 * <ul>
 * JPA2.0的(Criteria)条件API查询解释：
 * <li>
 * {@link Root}是一个查询表达式，它表示持久化实体的范围.Root< T>
 * 实际上表示：“对所有类型为 T 的实例计算这个查询。” 这类似于 JPQL 或 SQL 查询的 FROM 子句,都属性泛型
 * </li>
 * <li>
 * {@link javax.persistence.criteria.Predicate} 是计算结果为 true 或 false 的常见查询表达式形式。
 * </li>
 * <li>
 * {@link CriteriaBuilder}不仅是 {@link CriteriaQuery} 的工厂，同时也是查询表达式的工厂。
 * QueryBuilder 包含构造传统 JPQL 语法支持的所有查询表达式的 API 方法，并且还包含额外的方法。
 * </li>
 * </ul>
 *
 * @author poplar.yfyang
 * @version 1.0 11-10-17 下午10:54
 * @since JDK 1.5
 */
public abstract class LazyPawnDataModel<T> extends ExtendedDataModel<T> implements Arrangeable {
    /**
     * JPA的实体管理器
     */
    private EntityManager _entityManager;
    /**
     * 当前数据模型的RowKey
     */
    private Object _rowKey;
    /**
     *
     */
    private ArrangeableState _arrangeableState;


    /**
     * 列表数据
     */
    private List<T> _datas = Lists.newArrayList();
    /**
     * 数据Key以及数据实体对象组成的map，用来在{@link #getRowData()}函数中不用去查询数据库，而直接从这个map
     * 中获取。
     */
    private Map<Object, T> _dataMap = Maps.newHashMap();
    /**
     * 记录总数，判断是否重复查询的依据
     * 由于JSF的渲染方式，问题 {@link LazyPawnDataModel#getRowCount()}函数会执行多次，
     */
    private Integer _rowCount;

    /**
     * 实体类型
     */
    private Class<T> _entityClass;

    /**
     * 自定义查询类型
     */
    private List<QueryField> _customerQueryField;

    /**
     * 构造函数，设置实体管理器和实体类别
     *
     * @param arg_entityManager 实体管理器
     * @param arg_entityClass   实体类别
     */
    public LazyPawnDataModel(EntityManager arg_entityManager, Class<T> arg_entityClass) {
        super();

        this._entityManager = arg_entityManager;
        this._entityClass = arg_entityClass;
    }

    /**
     * 设置当前的缓存信息，从组件的排列状态中取得相关信息。
     *
     * @param arg_context 当前JSF上下文
     * @param arg_state   组件排列状态，包含过滤和排序字段的信息。
     */
    public void arrange(FacesContext arg_context, ArrangeableState arg_state) {
        if (arg_state != null) {
            this._arrangeableState = arg_state;
            this._dataMap.clear();
            //因为只有当总数为null时才会需要做判断的
            if (_rowCount != null) {
                for (FilterField filterField : arg_state.getFilterFields()) {
                    //当查询值不为空时，则清空总数
                    if (filterField.getFilterValue() != null) {
                        this._rowCount = null;
                        this._datas.clear();
                        break;
                    }
                }
            }
        }
    }

    @Override
    public void setRowKey(Object arg_key) {
        _rowKey = arg_key;
    }

    @Override
    public Object getRowKey() {
        return _rowKey;
    }


    /**
     * DataModal的迭代模型中的处理函数。
     *
     * @param arg_context 当前JSF上下文
     * @param arg_visitor 处理数据客户端，用于回调处理
     * @param arg_range   用于 <code>DataModel</code>的迭代处理接口
     * @param arg_arg     特定参数
     */
    @Override
    public void walk(FacesContext arg_context, DataVisitor arg_visitor, Range arg_range, Object arg_arg) {
        /**
         * 当点击页面分页条时清空当前的缓存数据。
         */
        if (arg_visitor instanceof DataTableRenderer) {
            _datas.clear();
        }
        SequenceRange sequenceRange = (SequenceRange) arg_range;
        //不能对SequenceRange进行对比，否则在点击行时会进行多次查询
        if (_datas.isEmpty()) {
            CriteriaQuery<T> criteriaQuery = createSelectCriteriaQuery();
            TypedQuery<T> query = _entityManager.createQuery(criteriaQuery);

            if (sequenceRange.getFirstRow() >= 0 && sequenceRange.getRows() > 0) {
                query.setFirstResult(sequenceRange.getFirstRow());
                query.setMaxResults(sequenceRange.getRows());
            }
            _datas = query.getResultList();
        }

        Object id;
        for (T t : _datas) {
            id = getId(t);
            _dataMap.put(id, t);
            arg_visitor.process(arg_context, id, arg_arg);
        }
    }

    /**
     * 如果在当前行索引中包含有效数据，则返回{@code true}.
     * 验证当前行索引下是否有有效数据
     *
     * @return true=有效数据；false=无效数据
     */
    @Override
    public boolean isRowAvailable() {
        return (getRowData() != null);
    }

    /**
     * 获取当前查询以及实体数据的总数。包含创建查询.
     * 在原始数据模型中，是返回当前数据模型的行的总数。
     *
     * @return 行总数
     */
    @Override
    public int getRowCount() {
        //判断是否进行过查询和总是是否为0
        if (_rowCount == null) {
            CriteriaQuery<Long> criteriaQuery = createCountCriteriaQuery();
            _rowCount = _entityManager.createQuery(criteriaQuery).getSingleResult().intValue();
        }
        return _rowCount;
    }

    /**
     * 获取当前行的索引数据。
     *
     * @return 当前行索引数据
     */
    @Override
    public T getRowData() {
        return _dataMap.get(_rowKey);
    }

    /**
     * 获取当前行的的索引
     *
     * @return 当前行索引
     */
    @Override
    public int getRowIndex() {
        T e = getRowData();
        return e == null ? -1 : _datas.indexOf(e);
    }

    /**
     * {@inheritDoc}
     *
     * @param arg_index
     */
    @Override
    public void setRowIndex(int arg_index) {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getWrappedData() {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     *
     * @param data
     */
    @Override
    public void setWrappedData(Object data) {
        throw new UnsupportedOperationException();
    }

    /**
     * 取得当前的自定义查询字段列表
     *
     * @return 自定义查询列表
     */
    public List<QueryField> getCustomerQueryField() {
        return _customerQueryField;
    }

    /**
     * 设置当前的自定义查询字段信息。
     *
     * @param _customerQueryField 自定义查询类型
     */
    public void setCustomerQueryField(List<QueryField> _customerQueryField) {
        this._customerQueryField = _customerQueryField;
    }

    /**
     * 取得实体的主键信息，也可以是其他的信息，主要用来判断数据唯一性
     *
     * @param arg_t 实体信息
     * @return 实体对象
     */
    protected abstract Object getId(T arg_t);

    /**
     * 创建当前JPA查询总数的查询构建器。
     *
     * @return 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     */
    private CriteriaQuery<Long> createCountCriteriaQuery() {
        CriteriaBuilder criteriaBuilder = _entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
        Root<T> root = criteriaQuery.from(_entityClass);

        Expression<Boolean> filterCriteria;
        if (_arrangeableState != null) {
            filterCriteria = createFilterCriteria(criteriaBuilder, root);
            filterCriteria = customerFilter(criteriaBuilder, root, filterCriteria);

            if (filterCriteria != null) {
                criteriaQuery.where(filterCriteria);
            }
            Expression<Long> count = criteriaBuilder.count(root);
            criteriaQuery.select(count);
        }
        return criteriaQuery;
    }

    /**
     * 取得当前JPA的查询构建器。引用实体的
     *
     * @return 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     */
    private CriteriaQuery<T> createSelectCriteriaQuery() {
        CriteriaBuilder criteriaBuilder = _entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(_entityClass);
        Root<T> root = criteriaQuery.from(_entityClass);
        Expression<Boolean> filterCriteria;

        if (_arrangeableState != null) {
            List<Order> orders = createOrders(criteriaBuilder, root);
            if (!orders.isEmpty()) {
                criteriaQuery.orderBy(orders);
            }

            filterCriteria = createFilterCriteria(criteriaBuilder, root);
            filterCriteria = customerFilter(criteriaBuilder, root, filterCriteria);

            if (filterCriteria != null) {
                criteriaQuery.where(filterCriteria);
            }

        }
        return criteriaQuery;
    }

    /**
     * 创建自定义的过滤条件。
     *
     * @param arg_criteriaBuilder 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     * @param arg_root            基础表达式
     * @param filterCriteria      计算表达式
     * @return 返回基本表达式
     */
    private Expression<Boolean> customerFilter(CriteriaBuilder arg_criteriaBuilder,
                                               Root<T> arg_root, Expression<Boolean> filterCriteria) {
        List<QueryField> queryFields = getCustomerQueryField();
        if (queryFields != null && !queryFields.isEmpty()) {
            for (QueryField queryField : queryFields) {
                String fieldName = queryField.getFieldName();
                Object fieldValue = queryField.getFieldValue();
                Expression<Boolean> predicate =
                        createFilterCriteriaForField(fieldName, fieldValue, arg_root, arg_criteriaBuilder);
                if (predicate == null) {
                    continue;
                }
                switch (queryField.getQuerytype()) {
                    case AND:
                        filterCriteria = (filterCriteria == null) ? predicate.as(Boolean.class) :
                                arg_criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
                    case NOT:
                        filterCriteria = (filterCriteria == null) ? arg_criteriaBuilder.not(predicate.as(Boolean.class)) :
                                arg_criteriaBuilder.and(filterCriteria, arg_criteriaBuilder.not(predicate.as(Boolean.class)));
                }
            }
        }
        return filterCriteria;
    }

    /**
     * 创建排序依据集合。
     *
     * @param arg_root            在FROM子句中的一个基本类型。查询类型总是引用实体。
     * @param arg_criteriaBuilder 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     * @return 排序依据集合
     */
    private List<Order> createOrders(CriteriaBuilder arg_criteriaBuilder, Root<T> arg_root) {
        List<Order> orders = Lists.newArrayList();
        List<SortField> sortFields = _arrangeableState.getSortFields();
        if (sortFields != null && !sortFields.isEmpty()) {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            for (SortField sortField : sortFields) {
                String propertyName = (String) sortField.getSortBy().getValue(facesContext.getELContext());

                Path<Object> expression = arg_root.get(propertyName);

                Order jpaOrder;
                SortOrder sortOrder = sortField.getSortOrder();
                if (sortOrder == SortOrder.ascending) {
                    jpaOrder = arg_criteriaBuilder.asc(expression);
                } else if (sortOrder == SortOrder.descending) {
                    jpaOrder = arg_criteriaBuilder.desc(expression);
                } else {
                    throw new IllegalArgumentException(sortOrder.toString());
                }
                orders.add(jpaOrder);
            }
        }
        return orders;
    }

    /**
     * 由外部调用，清理当前的总数值，这样业务Bean就可以清理，让数据模型可以重新取得总数了。
     */
    public void cleanRowCount() {
        _rowCount = null;
    }


    /**
     * 创建基于属性名称和值的过滤查询表达式。
     *
     * @param arg_propertyName    引用实体的属性名称
     * @param arg_filterValue     引用实体的属性值
     * @param arg_root            在FROM子句中的一个基本类型。查询类型总是引用实体。
     * @param arg_criteriaBuilder 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     * @return 类型安全查询表达式
     */
    protected Expression<Boolean> createFilterCriteriaForField(String arg_propertyName,
                                                               Object arg_filterValue,
                                                               Root<T> arg_root,
                                                               CriteriaBuilder arg_criteriaBuilder) {
        String stringFilterValue = (String) arg_filterValue;
        if (Strings.isNullOrEmpty(stringFilterValue)) {
            return null;
        }
        stringFilterValue = stringFilterValue.toLowerCase(_arrangeableState.getLocale());

        Path<String> expression = arg_root.get(arg_propertyName);
        Expression<Integer> locator = arg_criteriaBuilder.locate(arg_criteriaBuilder.lower(expression), stringFilterValue, 1);
        return arg_criteriaBuilder.gt(locator, 0);
    }

    /**
     * 创建过滤查询表达式。
     *
     * @param arg_criteriaBuilder 用于构造条件查询，复合选择，表达式，谓词，排序的JPA对象
     * @param arg_root            在FROM子句中的一个基本类型。查询类型总是引用实体。
     * @return 查询表达式类型
     */
    private Expression<Boolean> createFilterCriteria(CriteriaBuilder arg_criteriaBuilder,
                                                     Root<T> arg_root) {
        Expression<Boolean> filterCriteria = null;
        List<FilterField> filterFields = _arrangeableState.getFilterFields();
        if (filterFields != null && !filterFields.isEmpty()) {
            FacesContext facesContext = FacesContext.getCurrentInstance();

            for (FilterField filterField : filterFields) {

                Object filterValue = filterField.getFilterValue();
                String propertyName = (String) filterField.getFilterExpression().
                        getValue(facesContext.getELContext());

                Expression<Boolean> predicate = createFilterCriteriaForField(propertyName, filterValue, arg_root, arg_criteriaBuilder);

                if (predicate == null) {
                    continue;
                }
                filterCriteria = (filterCriteria == null) ? predicate.as(Boolean.class) :
                        arg_criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
            }

        }
        return filterCriteria;
    }
}
